gRPC 使用-配置环境
Go 的 gRPC 官方库 Go 的 gRPC Quick start
搭建环境
安装 protoc
安装编译器,参考 Protocol Buffer Compiler Installation
# 最简单的方式
sudo apt install -y protobuf-compiler
也可以选择手动安装 官方资源库:
# 这里可以只下载对应的编译器的
wget https://github.com/protocolbuffers/protobuf/releases/download/v3.19.1/protoc-3.19.1-linux-x86_64.zip
unzip protoc-3.19.1-linux-x86_64.zip -d protobuf-3.19.1/
# 添加这个执行文件
vim ~/.bashrc
export PROTOBUF=/home/alsritter/tool/go/protobuf-3.19.1/
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin:$PROTOBUF/bin
# 更新
source ~/.bashrc
# 检查是否安装完成
protoc --version
安装 Golang 的依赖
安装 Golang 的编译插件
$ go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26
$ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1
更新路径
# 这一步其实可以省略
export PATH="$PATH:$(go env GOPATH)/bin"
将 Protoc Plugin 的可执行文件从 $GOPATH
中移动到 $GOBIN
下
mv $GOPATH/bin/protoc-gen-go $GOROOT/bin/
安装 gRPC 框架
go get -u google.golang.org/grpc
编译命令
$ protoc --help
Usage: protoc [OPTION] PROTO_FILES
-IPATH, --proto_path=PATH 指定搜索路径
--plugin=EXECUTABLE:
....
--cpp_out=OUT_DIR Generate C++ header and source.
--csharp_out=OUT_DIR Generate C# source file.
--java_out=OUT_DIR Generate Java source file.
--js_out=OUT_DIR Generate JavaScript source.
--objc_out=OUT_DIR Generate Objective C header and source.
--php_out=OUT_DIR Generate PHP source file.
--python_out=OUT_DIR Generate Python source file.
--ruby_out=OUT_DIR Generate Ruby source file
@<filename> proto文件的具体位置
--proto_path
或者-I
参数用以指定所编译源码(包括直接编译的和被导入的 proto 文件)的搜索路径--go_out
:参数之间用逗号隔开,最后用冒号来指定代码目录架构的生成位置--go_out=plugins=grpc
:参数来生成 gRPC 相关代码,如果不加 plugins=grpc,就只生成 message 数据
其中:
--go_out=plugins=grpc,paths=import:.
。注意一下 paths 参数,他有两个选项,import 和 source_relative 。默认为 import ,代表按照生成的 go 代码的包的全路径去创建目录层级,source_relative 代表按照 protobuf 源文件的目录层级去创建 go 代码的目录层级,如果目录已存在则不用创建
# 这里就是读取 protos 目录下的 proto 文件
protoc -I . --go_out=plugins=grpc:. --go_opt=paths=source_relative protos/*.proto
实例
# 进入到 proto 目录中,输入
$ protoc *.proto --go_out=plugins=grpc:. --go_opt=paths=source_relative
# 或者在项目的根目录输入
# --go_opt=paths=source_relative 表示要用相对路径产生
$ protoc -I <src_proto_folder> --go_out=plugins=grpc:<dist_directory> --go_opt=paths=source_relative <src_proto_file_path>
$ protoc -I proto/jubox --go_out=plugins=grpc:proto/jubox --go_opt=paths=source_relative proto/jubox/jubox.proto
# 產生編譯檔(產生一般的 .pb.go 檔,但沒有使用 gRPC plugin
$ protoc -I=$SRC_DIR --go_out=$DST_DIR $SRC_DIR/addressbook.proto # 預設根據 go_package 路徑
# 產生 proto 檔,預設將根據 proto 中的 go_package 路徑
$ protoc -I=$SRC_DIR --go_out=$DST_DIR $SRC_DIR/addressbook.proto
# 另一種寫法會 build 出兩隻檔案,一支是 proto buffer(foobar.pb.go),一支是 gRPC 用的檔案(foobar_grpc.pb.go)
$ protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative {proto_filename}.proto
如何 import 其它文件
有时使用其它的三方插件,总是不可避免的会引用到其它的包,如下所示:
syntax = "proto3";
package helloworld;
import "google/api/annotations.proto";
// ...
那么如何安装这些 proto 呢?
其实很简单,引入这些三方 proto,只需在执行 protoc 命令时指定对应的路径就行了
首先下载 proto
go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway@v1.14.5
下载之后就能在 github.com\grpc-ecosystem\grpc-gateway@v1.14.5\third_party\googleapis\google\api
目录下找到对应的 proto 文件了
执行完上述命令后,就会将 protoc-gen-grpc-gateway
下载到电脑的 GOPATH 下,自己电脑的 GOPATH 可以通过命令 go env
查看。
所以对应的就是
$GOPATH\pkg\mod\github.com\grpc-ecosystem\grpc-gateway@v1.14.5\third_party\googleapis
所以引用路径就是
# 注意: 路径中要用 '/'
protoc -I $GOPATH/pkg/mod/github.com/grpc-ecosystem/grpc-gateway@v1.14.5/third_party/googleapis \
--go_out=plugins=grpc:. ./*.proto
简单的使用例
编写一个 Hello World 程序
项目结构:
|—— hello/
|—— client/
|—— main.go // 客户端
|—— server/
|—— main.go // 服务端
|—— proto/
|—— hello/
|—— hello.proto // proto 描述文件
|—— hello.pb.go // proto 编译后文件
编写 proto 文件
编写描述文件:hello.proto
注意:下面这个 go_package 的路径必须有一个 /
,网上的很多教程都是以前的,因为编译 .pb.go
调用的是 protoc-gen-go
插件完成,而这个插件 v1.4.0 以上的版本就要求必须加 /
了
具体生成的地址参考 Correct format of protoc go_package?
syntax = "proto3"; // 指定proto版本
package hello; // 指定默认包名
// 指定 golang 包名,编译到当前路径,并且使用 hello 包(前面是导出路径, ; 后面是包名)
option go_package = "./;hello";
// 定义Hello服务
service Hello {
// 定义SayHello方法
rpc SayHello(HelloRequest) returns (HelloResponse) {}
}
// HelloRequest 请求结构
message HelloRequest {
string name = 1;
}
// HelloResponse 响应结构
message HelloResponse {
string message = 1;
}
hello.proto
文件中定义了一个 Hello Service,该服务包含一个 SayHello 方法,同时声明了 HelloRequest 和 HelloResponse 消息结构用于请求和响应。
客户端使用 HelloRequest 参数调用 SayHello 方法请求服务端,服务端响应 HelloResponse 消息。一个最简单的服务就定义好了。
编译生成 Go 文件
编译生成 .pb.go
文件
# 编译 hello.proto
#$ protoc -I . --go_out=plugins=grpc:. ./hello.proto
$ protoc --go_out=./proto \
--go-grpc_out=require_unimplemented_servers=false:./proto proto/hello.proto
这里实际上使用了两个插件,protoc-gen-go
、protoc-gen-go-grpc
,后者是生成 gRPC 代码,但是这个 protoc-gen-go-grpc
插件依赖于前者生成的 proto 结构体
如下:protoc-gen-go
生成的结构体:
// HelloRequest 请求结构
type HelloRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
}
注意,不要编辑编译生成的 hello.pb.go
文件
编写服务端
实现服务端接口 server/main.go
package main
import (
"context"
"fmt"
"net"
pb "stgrpc/proto/hello"
"google.golang.org/grpc"
"google.golang.org/grpc/grpclog"
)
const (
// Address gRPC服务地址
ADDRESS = "127.0.0.1:50052"
)
// 定义 helloService 并实现约定的接口
type helloService struct{}
// HelloService Hello 服务
var HelloService = helloService{}
// SayHello 实现 Hello 服务接口
func (h helloService) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) {
resp := new(pb.HelloResponse)
resp.Message = fmt.Sprintf("Hello %s.", in.Name)
return resp, nil
}
func main() {
listen, err := net.Listen("tcp", ADDRESS)
if err != nil {
grpclog.Fatalf("Failed to listen: %v", err)
}
// 实例化 grpc Server
s := grpc.NewServer()
// 注册 HelloService
pb.RegisterHelloServer(s, HelloService)
grpclog.Infoln("Listen on " + ADDRESS)
s.Serve(listen)
}
服务端引入编译后的 proto 包,定义一个空结构用于实现约定的接口,接口描述可以查看 hello.pb.go
文件中的 HelloServer 接口描述。实例化 grpc Server 并注册 HelloService,开始提供服务。
注意:因为这里使用了 grpclog 库,它内部调用的 LoggerV2 默认为 ERROR 级别
所以需要自己设置这个 GRPC_GO_LOG_SEVERITY_LEVEL 系统变量的级别
# ERROR、WARNING、INFO
$ export GRPC_GO_LOG_SEVERITY_LEVEL=INFO
# 再次运行就能打印日志了
$ go run server/main.go
# 2021/11/07 21:08:15 INFO: Listen on 127.0.0.1:50052
编写客户端
实现客户端调用 client/main.go
package main
import (
pb "stgrpc/proto/hello" // 引入proto包
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/grpclog"
)
const (
// Address gRPC服务地址
ADDRESS = "127.0.0.1:50052"
)
func main() {
// 连接
conn, err := grpc.Dial(ADDRESS, grpc.WithInsecure())
if err != nil {
grpclog.Fatalln(err)
}
defer conn.Close()
// 初始化客户端
c := pb.NewHelloClient(conn)
// 调用方法
req := &pb.HelloRequest{Name: "Hello gRPC !"}
res, err := c.SayHello(context.Background(), req)
if err != nil {
grpclog.Fatalln(err)
}
grpclog.Info(res.Message)
}
检查控制台